	/**
	 * Adds a property resolver from the given source property name to the target
	 * property name, stating that the passed valueResolutionFunction will be used
	 * to perform the value resolution.  To state that the entire source instance
	 * should be passed to the resolution function, pass null for the source property.
	 */
	public function addPropertyResolver( resolver : PropertyResolver ) : Mapping
	{
		if ( !metadataBuilder )
		{
			queuedPropertyResolvers.push( resolver )
		}
		else
		{
			var sourceProperties : PropertyMetadata = metadataBuilder.buildMetadataForClass( source ).propertyMetadata	
			var targetProperties : PropertyMetadata = metadataBuilder.buildMetadataForClass( target ).propertyMetadata	
				
			if ( resolver.sourceProperty && !sourceProperties.containsProperty( resolver.sourceProperty ) )
			{
				throw new Error( "Can't add a custom property resolver for the source property \"" + resolver.sourceProperty + "\" because no such property exists on the source class, " + Type.forClass( source ).fullName );
			}
			
			if ( !targetProperties.containsProperty( resolver.targetProperty ) )
			{
				throw new Error( "Can't add a custom property resolver for the target property \"" + resolver.targetProperty + "\" because no such property exists on the target class, " + Type.forClass( target ).fullName );
			}
			
			var resolution : PropertyResolution = new PropertyResolution()
			
			resolution.sourceProperty = resolver.sourceProperty != null ? sourceProperties.getProperty( resolver.sourceProperty ) : null
			resolution.targetProperty = targetProperties.getProperty( resolver.targetProperty )
			resolution.valueResolutionFunction = resolver.valueResolutionFunction
			resolution.explicit = true
			
			resolvedProperties[ resolver.targetProperty ] = resolution;
			
			if ( unresolvedPropertiesByName[ resolver.targetProperty ] != null )
			{
				delete unresolvedPropertiesByName[ resolver.targetProperty ]
			}
			
			if ( unresolvedPropertiesByType[ resolver.targetProperty ] != null )
			{
				delete unresolvedPropertiesByType[ resolver.targetProperty ]
			}
		}
		
		return this
	}
	
	public function ignoreProperty( propertyName : String ) : Mapping
	{
		ignoredProperties.push( propertyName )
			
		return this
	}
	
	public function setAggregatedType( propertyName : String, clazz : Class ) : Mapping
	{
		if ( metadataBuilder )
		{
			var targetProperties : PropertyMetadata = metadataBuilder.buildMetadataForClass( target ).propertyMetadata	
			
			if ( !targetProperties.containsProperty( propertyName ) )
			{
				throw new Error( "Can't set the aggregated type for the target property \"" + propertyName + "\" because no such property exists on the target class, " + Type.forClass( target ).fullName );
			}
			
			aggregatedPropertyTargetTypes[ propertyName ] = clazz
		}
		else
		{
			queuedAggregates.push( [ propertyName, clazz ] )
			
		}
		return this
	}
	
	/**
	 * States if a given property (of the target class!) has been assigned
	 * a value resolution function.
	 */
	public function hasResolvedProperty( propertyName : String ) : Boolean
	{
		return resolvedProperties[ propertyName ] != null;
	}
	
	/**
	 * States if a given property (of the target class!) has been assigned
	 * is missing a value resolution function.
	 */
	public function hasUnresolvedProperty( propertyName : String ) : Boolean
	{
		return unresolvedPropertiesByName[ propertyName ] != null || unresolvedPropertiesByType[ propertyName ] != null
	}
	
	/**
	 * Checks for unresolved propertyies by name and type, printing suggested resolutions
	 * to the console and throwing an error if unresolved properties are found.
	 */
	public function assertMappingIsValid() : Boolean
	{
		var name  type="array" = []
		var type  type="array" = []
		var err : InvalidMappingsError
		var message : String = "MAPPING PROBLEMS: " + Type.forClass(source).name + " -> " + Type.forClass(target).name + "\n"
		
		// If some properties type mapping relies on Mappings establish _after_ this one
		// was added to the mapper, we need to check their resolution.
		createLateMappedPropertyResolutions()
		
		for each ( var byName : UnresolvedNameMapping in unresolvedPropertiesByName )
		{
			if ( ignoredProperties.indexOf( byName.targetProperty.name ) == -1 )
			{
				name.push( byName )
					
				message += byName.toString() + "\n"
			}
		}
		
		for each ( var byType : UnresolvedTypeMapping in unresolvedPropertiesByType )
		{
			if ( ignoredProperties.indexOf( byType.targetProperty.name ) == -1 )
			{
				type.push( byType )
				
				message += byType.toString() + "\n"
			}
		}

		// Validate our aggregated target types
		var missingTypeAggregation : Boolean = false;
		for each ( var targetProperty : * in aggregatedTargetProperties )
		{
			if ( !aggregatedPropertyTargetTypes.hasOwnProperty( targetProperty.name )
				&& (
					!resolvedPropertyMap.hasOwnProperty( targetProperty.name )
					|| PropertyResolution( resolvedPropertyMap[ targetProperty.name ] ).explicit == false
				)
			)
			{
				missingTypeAggregation = true
					
				message += "\n-- MISSING AGGREGATED TYPE: For the target type " 
					+ Type.forClass( target ).fullName + "'s \"" + targetProperty.name + "\"\n\t"
					+ " property, you haven't stated what class to use to fill the collection.\n\t"
					+ " This means that the mapper can't map this property because it wouldn't know what types to create!\n\t"
					+ " Please call .setAggregatedType( \"" + targetProperty.name + "\", TypeName ) or add a manual property resolver to handle this property."
			}
		}
		
		if ( name.length > 0 || type.length > 0 || missingTypeAggregation )
		{
			trace( message )
			var e : InvalidMappingsError =  new InvalidMappingsError( name, type, message )
			throw( e )
		}		
		
		validated = true
			
		return true
	}
